package navi;

import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;
import com.sun.tools.attach.spi.AttachProvider;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.Properties;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.management.JMX;
import javax.management.MBeanServer;
import javax.management.MBeanServerConnection;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;

/**
 *
 * @author Ivan
 */
public class JAppDomain {

    //list of domains
    public static ArrayList<JAppDomain> domains = new ArrayList<>();

    //class path for new JVM    
    public String ClassPath;

    //java home
    public String JavaHome;

    //file separator
    public String FileSeparator;

    //process of remote JVM
    Process childProcess;

    //connection to remote JVM JMX
    MBeanServerConnection remote;

    /**
     * Clean up
     */
    static {
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            ArrayList<JAppDomain> copy = new ArrayList<>();
            copy.addAll(domains);
            for (JAppDomain dom : copy) {
                dom.shutdown();;
            }
        }));
    }

    /**
     * Initialization
     */
    public JAppDomain() {
        ClassPath = System.getProperty("java.class.path");
        JavaHome = System.getProperty("java.home");
        FileSeparator = System.getProperty("file.separator");
        domains.add(this);
    }

    /**
     * Starts new app domain.
     * Starts JVM,sends it this PID and address of callback bean
     */
    public synchronized void start() {
        try {
            //start local callback waiting bean
            String locguid=UUID.randomUUID().toString();
            ObjectName name = new ObjectName("navi:type=" + locguid);
            ParentComm comm=new ParentComm();
            comm.domain=this;
            MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); 
            mbs.registerMBean(comm, name); 
            //get my PID
            String processName = java.lang.management.ManagementFactory.getRuntimeMXBean().getName();
            String ppid=processName.split("@")[0];
            //start other JVM,send my PID and my callback bean address
            String whatexe = JavaHome +FileSeparator + "bin" + FileSeparator + "java";
            String cmd = whatexe +  " -Djappdomain.parentpid="+ppid+" -Djappdomain.parentcomm="+locguid+" -cp \"" + ClassPath + "\" navi.JAppDomainRemoteKick ";
            childProcess = Runtime.getRuntime().exec(cmd);
            boolean al = childProcess.isAlive();
            if (!al) throw new Exception("New JVM not started up");
            //wait for callback
            this.wait();
        } catch (Exception ex) {
            Logger.getLogger(JAppDomain.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    
     /**
     * Callback from client received.Now child PID is known, connect to it.
     */
    public synchronized void startCallback(String childpid) {
        try {
            //get JVM of child PID
            final AttachProvider attachProvider = AttachProvider.providers().get(0);
            VirtualMachineDescriptor descriptor = null;
            for (VirtualMachineDescriptor virtualMachineDescriptor : attachProvider.listVirtualMachines()) {
                String id = virtualMachineDescriptor.id();
                if (id.equals(childpid)) {
                    descriptor = virtualMachineDescriptor;
                    break;
                }
            }
            if (descriptor == null) {
                throw new RuntimeException("Not attached");
            }
            //connect tio JVM JMC
            final VirtualMachine virtualMachine = attachProvider.attachVirtualMachine(descriptor);
            Properties props = virtualMachine.getSystemProperties();
            String home = props.getProperty("java.home");
            String agent = home + FileSeparator + "lib" + FileSeparator + "management-agent.jar";
            virtualMachine.loadAgent(agent, "com.sun.management.jmxremote");
            final Object portObject = virtualMachine.getAgentProperties().get("com.sun.management.jmxremote.localConnectorAddress");
            final JMXServiceURL target = new JMXServiceURL(portObject + "");
            final JMXConnector connector = JMXConnectorFactory.connect(target);
            remote = connector.getMBeanServerConnection();
            //callback received, continue
            this.notify();
        } catch (Exception ex) {
            Logger.getLogger(JAppDomain.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    
    
    /**
     * Create remote objects in remote AppDomain
     * @param klass concrete class to be created
     * @param iface mbean interface it implements
     * @return 
     */
    public Object create(Class klass, Class iface) {
        try {
            String locguid=UUID.randomUUID().toString();
            ObjectName name = new ObjectName("navi:type=" + locguid);
            ObjectInstance oi=remote.createMBean(klass.getName(),name);
            Object x=JMX.newMBeanProxy(remote,name,iface);
            return x;
        } catch (Exception ex) {
            Logger.getLogger(JAppDomain.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        }
    }

    /**
     * Shutdown domain
     */
    public void shutdown() {
        try {
            childProcess.destroy();
            domains.remove(this);
        } catch (Exception ex) {
            Logger.getLogger(JAppDomain.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}
